/*
 * Author: nicolas.bredeche(@)upmc.fr
 * Created in 2005 (copy from picotoolbox) 
 */

package picoevo.tools;


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Properties;

public class ParameterSet extends Properties implements Cloneable {

    // ### Data ###
    
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private ArrayList<String> _keys = new ArrayList<String>();
    private ArrayList<Object> _values = new ArrayList<Object>();
    private String _name = "(no name)";
    
    private boolean _debug = false; // true: generates exception if key not found; false: compatibility with Properties
    
    
    // ### Constructors ###
    
    public ParameterSet ()
    { }
    
    public ParameterSet ( String __name )
    {
    		this._name = __name;
    }
    
    // ### setProperty methods ###
    
    public Object setProperty(String __key, Object __value)
    {
    		Object returnValue = null;
    		if ( this.contains(__key) == true )
    		{
    			returnValue = this.getProperty(__key);
    			this.remove(__key);
    		}
    	
    		_keys.add(__key);
    		_values.add(__value);
    		return returnValue;
    }

    /**
     * override the default Properties'setProperty method.
     * return value is provided for compatibility.
     */
	public Object setProperty(String __key, String __value)
	{
		Object returnValue = null;
		if ( this.contains(__key) == true )
		{
			returnValue = this.getProperty(__key);
			this.remove(__key);
		}
		
		super.put(__key,__value);
		
		return returnValue;
	}
	
	public Object setProperty(String __key, int __value)
	{
		return setProperty(__key,String.valueOf(__value));
	}

	public Object setProperty(String __key, double __value)
	{
		return setProperty(__key,String.valueOf(__value));
	}
	
	public Object setProperty(String __key, boolean __value)
	{
		return setProperty(__key,String.valueOf(__value));
	}
	
	/**
	 * override the hashtable's put method -- put method should not be used -- use setProperty methods instead.
	 * n! devnotes: put cannot be overriden because of loadProperty methods 
	 */
	/*
	public Object put (Object __key, Object __value)
	{
		Display.warning("ParameterSet - avoid using put method (use setProperty instead).");
        try {
			throw new Exception();
		} catch (Exception e) {
			e.printStackTrace();
		}
		Display.critical("stop.");
		return null;
	}
	*/

	// ### getProperty methods ###
	
	/**
	 * Searches through *all* properties (object, string, boolean, int). Important issue : the ParameterSet is an extension of the Properties class that is not limited to storing String, but also makes it possible to store any kind of Object which can be used directly afterwards (with the appropriate cast). see also getProperty. 
	 * @param __key name of the target property
	 * @return the Object value of the property (return null if not found (stop if debug mode))
	 */
	public Object getObjectProperty(String __key)
	{
		// searches through Properties internal storage
		String target = super.getProperty(__key);
		if ( target != null )
			return target;
		
		// searches through ParameterSet internal storage
		int i;
	    for ( i = 0 ; i != _keys.size() ; i++ )
	            if ( (((String)_keys.get(i)).compareTo(__key)) == 0 )
	            		return ( _values.get(i) );
	
	    // not found
	    Display.warning("ParameterSet \""+this._name+"\" : key ["+__key+"] does not exist.");
        if ( _debug == true )
        {
	        try {
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
			Display.critical("stop.");
        }
	    
	     return null;
	}

	/**
	 * Searches through *all* properties (object, string, boolean, int -- differ from Properties class). see also getObjectProperty.
	 * @param __key name of the target property
	 * @return the String value of the property
	 */
	public String getProperty(String __key)
	{
		// searches through Properties internal storage
		String target = super.getProperty(__key);
		if ( target != null )
			return target;
		
		// searches through ParameterSet internal storage
	    int i;
	    for ( i = 0 ; i != _keys.size() ; i++ )
	            if ( (((String)_keys.get(i)).compareTo(__key)) == 0 )
	            		return ( (_values.get(i)).toString());
	            
	    // not found
        Display.warning("ParameterSet \""+this._name+"\" : key ["+__key+"] does not exist.");
        
        if ( _debug == true )
        {
	        try { // useful for user debugging.
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
			Display.critical("stop.");
        }
	    
	    return null; 
	}
	
	/**
	 * same as getProperty(String) method, but return the defaultValue if key not found.
	 */
	public String getProperty(String __key, String __defaultValue)
	{
		// searches through Properties internal storage
		String target = super.getProperty(__key);
		if ( target != null )
			return target;
		
		// searches through ParameterSet internal storage
	    int i;
	    for ( i = 0 ; i != _keys.size() ; i++ )
	            if ( (((String)_keys.get(i)).compareTo(__key)) == 0 )
	            		return ( (_values.get(i)).toString());
	            
	    return __defaultValue; 
	}
	
	public int getIntProperty(String __key)
	{
		return (Integer.parseInt(this.getProperty(__key)));
	}
	
	public double getDoubleProperty(String __key)
	{
		return (Double.parseDouble(this.getProperty(__key)));
	}
	
	public boolean getBooleanProperty(String __key)
	{
		String s = this.getProperty(__key);
		if ( s.compareTo("true") == 0 || s.compareTo("TRUE") == 0 )
			return (true);
		else
			if ( s.compareTo("false") == 0 || s.compareTo("FALSE") == 0 )
				return (false);
			else 
				Display.critical(this.getClass().getName()+"::getBooleanProperty(.) - not a boolean (" + s + ").");
		return false; // never used.
	}

	public String getStringProperty(String __key)
	{
		return ((this.getProperty(__key)));
	}

	
	public int getIntProperty(String __key, int __defaultValue)
	{
		if ( !this.contains(__key) )
			return __defaultValue;
		else 
			return this.getIntProperty(__key);
	}
	
	public double getDoubleProperty(String __key, double __defaultValue)
	{
		if ( !this.contains(__key) )
			return __defaultValue;
		else 
			return this.getDoubleProperty(__key);
	}
	
	public boolean getBooleanProperty(String __key, boolean __defaultValue)
	{
		if ( !this.contains(__key) )
			return __defaultValue;
		else 
			return getBooleanProperty(__key);
	}

	public String getStringProperty(String __key, String __defaultValue)
	{
		if ( !this.contains(__key) )
			return __defaultValue;
		else
			return this.getStringProperty(__key);
	}

	
	// ### Other methods ###
	

	/**
	 * check if targeted key exists (check both Properties and ParameteSet lists)
	 */
	public boolean contains (String __key)
	{
		if ( super.getProperty(__key) != null ) // check Properties list
			return true;
		
	    return containsObjectKey(__key); // check ParameterSet list
	}
	
	private boolean containsObjectKey (String __key) // only ParameterSet list
	{
	    for ( int i = 0 ; i != _keys.size() ; i++ )
	    		if ( (((String)_keys.get(i)).compareTo(__key)) == 0 )
	    			return true;
    	return false;
	}
	
	/**
	 * remove key/value if any (search both Properties and ParameterSet lists). Returns false if no target found.
	 * @param __key
	 */
	public boolean remove (String __key)
	{
		if ( super.containsKey(__key) == true )
		{
			super.remove(__key);
			return true;
		}
		return removeObjectKey(__key);
	}
	
	private boolean removeObjectKey(String __key)
	{
	    for ( int i = 0 ; i != _keys.size() ; i++ )
	    		if ( (((String)_keys.get(i)).compareTo(__key)) == 0 )
	    		{
	    			this._keys.remove(i);
	    			this._values.remove(i);
	    			return true;
	    		}
	    	return false;
	}
    
	public void clear ()
	{
		super.clear();
		this._keys.clear();
		this._values.clear();
	}
	
	/**
	 * Load *properties* from a file -- warning, everything is loaded as a (key, string value) couple.
	 * After this, Properties'internal list may be changed, but ParameterSet'internal list remains unmodified.
	 * @param fileName
	 * return true if file exists and not empty
	 */
    public boolean loadProperties(String fileName) {
        if (fileName.equals(""))
        {
        	   Display.warning("Loading Properties : filename empty.");
            return false;
        }
        try {
            FileInputStream fis = new FileInputStream(fileName);
            this.load(fis);
            fis.close();
        } 
        catch(IOException e) { 
            //e.printStackTrace();
            Display.warning("Loading Properties : File \"" + fileName + "\" not found.");
            return false;
        }
        return true;
    }
    
	// ### monitoring methods ###
	
    public void setDebug ( boolean __value )
    {
    		this._debug = __value;
    }
	
	public void displayInformation()
	{
		Display.info("\n### PROPERTIES and PARAMETER sets : \"" + this._name + "\" ###" );
		Display.info("Listing all registered keys/values.");
		Display.info("{* Properties internal list *}");
		//Display.info(""+ super.toString());
		super.list(System.out);
		Display.info("{* ParameterSet internal list *}");
		for ( int i = 0 ; i != _keys.size() ; i++ )
                 Display.info("["+((String)_keys.get(i))+"] => ["+_values.get(i)+"]");
		Display.info("{* end *}.");
	}	
	
	public String getString()
	{
		String s = "";

		// properties internal storage
		StringWriter writer = new StringWriter();
		this.list(new PrintWriter(writer));
		s += writer.getBuffer().toString(); 
		
		// properties extra storage
		s += "\n-- listing extras --\n";
		for ( int i = 0 ; i != _keys.size() ; i++ )
			s += "["+((String)_keys.get(i))+"] => ["+_values.get(i)+"]\n";
		
		return s;
	}	

}
